======================================================================== = Systemprogrammierung in PCQ-Pascal = = Kurs für AmigaGadget/Purity - Teil IV = ======================================================================== Bisherige Kursteile : Teil I : Voraussetzungen,Screens,Windows Teil II : Einfache Grafikausgabe Teil III : Weitere Grafikbefehle, einige nützliche Routinen Erst einmal ein fröhliches Hallo an all die interessierten Pascal- Programmierer, die sich wieder zusammengefunden haben, um hier der Lust der Systemprogrammierung zu frönen. Einen wunderschönen guten Morgen ! Heute wollen wir endlich damit beginnen, den Anwender in unsere Programme mit einzubeziehen. Da wir Hand in Hand mit Intuition arbeiten, existiert da für uns natürlich ein Element, das uns bei geringem Aufwand ein großes Ergebniss liefert - die IntuitionMessage oder kurz IntuiMessage. Sie arbeitet über sogenannte IDCMPs, IntuitionDirectCommunictionMessagePorts. Wie die meisten Intuition-Strukturen ist sie in "Include:Intuition/Intuition.i" definiert und siehr folgendermaßen aus : IntuiMessage = record ExecMessage : Message; die "eigentliche" Messagem wie sie von Exec verwaltet wird Class : Integer; nähere Informationen über die Art der Message (z.dt. Nachricht) Code : Short; speziellere Informationen über die Message, z.B. bei gedrückten Mausknopf, ob er losgelassen wurde oder gedrückt, etc. Qualifier : Short; bei Tastaturabfrage Informationen über benutzte Sondertasten etc. IAddress : Address; enthält spezielle Informationen, wie z.B. die Adresse des aktivierten Gadgets MouseX, die Mauskoordinaten, bei Empfang MouseY : Short; der Message Seconds, die Werte der Systemuhr zur Zeit Micros : Integer; des Empfangs der Nachricht IDCMPWindow : Address; die Adresse des Windows, aus dem die Message kam SpecialLink : ^IntuiMessage; end; IntuiMessagePtr = ^IntuiMessage; Sieht komplex und kompliziert aus, erweist sich jedoch im täglichen Gebrauch als sehr praktisch und einfach. Wie kommen wir nun an so eine Message ran ? Es empfiehlt sich - der Einfachkeit halber - ersteinmal mit den uns aus diesem Kurs schon bekannten Elementen zu arbeiten - in unserem Fall mit Windows. Wir erinnern uns, oder lesen es nach, daß wir im Teil 1 dieses Kurses ein Element der NewWindow-Struktur ausdrücklich aufgespart haben, eine Integer-Variable des Namens IDCMPFlags. Nun können wir etwas mehr damit anfangen, wissen wir doch jetzt grob, was IDCMP für uns bedeutet. Die Vermutung liegt nahe, daß man dieses Feld mit Informationen füllen kann, die die Art der IntuiMessages bestimmt, die in diesem Window empfangen werden können. Das ist sehr sinnvoll, da es eine Unzahl von IntuiMessages gibt und es für den Programmierer enorm aufwendig wäre, stets nur die gewünschten herauszufiltern. Eine kurze Übersicht über die wichtigsten der IDCMP-Flags, natürlich in "Include:Intuition/Intuition.i" zu finden : SIZEVERIFY_f wenn der Benutzer versucht, die Größe des Fensters zu ändern, wird diese Message gesendet. Intuition wartet so lange, bis man diese IntuiMsg beantwortet hat. NEWSIZE_f IntuiMsg wird gesendet, wenn die Größe des Fensters vom Benutzer geändert wurde REFRESHWINDOW_f spielt bei Windows eine Rolle, die nur über SIMPLE_REFRESH verfügen und wird gesendet, wenn der Fensterinhalt zerstört wurde, der Programmierer sich also darum kümmern muß, ihn wieder aufzubauen. MOUSEBUTTONS_f diese IntuiMsg erhält man, wenn der Benutzer die linke Maustaste gedrückt hat. Hat man auch noch das Flag RMBTRAP in der entsprechenden NewWindowStruktur definiert, so erhält man diese Message auch bei Betätigung des rechten Mausknopfs. Nähere Informationen zu der Art dieser Message stehen im Feld Code des IntuiMsg : SELECTUP : linke Maustaste losgelassen SELECTDOWN : linke Maustaste gedrückt MENUUP : rechte Maustaste losgelassen MENUDOWN : rechte Maustaste gedrückt MOUSEMOVE_f sollte gesetzt sein, wenn man in der IntuiMessage -Struktur die Mauskoordinaten aktualisiert haben möchte. GADGETDOWN_f ein Gadget wurde angeklickt GADGETUP_f ein Gadget wurde wieder losgelassen (die linke Maustaste wurde über einem Gadget losgelassen) MENUPICK_f ein Menüpunkt wurde ausgewählt, nähere Informationen darüber in Variable Code und wenn wir uns in diesem Kurs näher damit beschäftigen. CLOSEWINDOW_f das CloseGadget des Windows wurde angewählt RAWKEY_f eine Taste wurde gedrückt. Der Tastaturcode (nicht der Ascii-Code) befindet sich in der Variable Code. NEWPREFS_f es wurden einige Preferences-Werte vom Benutzer geändert DISKINSERTED_f eine Diskette wurde in ein Laufwerk eingelegt DISKREMOVED_f eine Diskette wurde einem Laufwerk entnommen ACTIVEWINDOW_f ein Fenster wurde aktiviert INACTIVEWINDOW_f ein Fenster wurde deaktiviert VANILLAKEY_f wie RAWKEY_f, nur daß diesmal der ASCII-Code in Code gespeichert wird INTUITICKS_f solange das Fenster geöffnet ist, behämmert Intuition den Programmierer mit diesem Signal Das "_f" am Ende dient übrigens dazu, die Flags von oft gleichnamigen Intuition-Prozeduren und -Funktionen zu unterscheiden. Die Flags müssen in das IDCMP-Feld der NewWindow-Struktur eingetragen werden und stehen dei Empfang einer Nachricht im Feld "Class" ! Nun kommen wir zu einem Beispiel : Wir wollen ein Fenster aufmachen, das durch Druck auf das Closegadget geschlossen wird und dabei das Programm beendet. "Einfach" möchte man vorschnell denken und man hätte sogar recht :-) Denken wir uns die Schritte durch : 1. Fensterstruktur festlegen, dabei muß das Feld IDCMPFlags mit CLOSEWINDOW_f gefüllt werden. 2. Öffnen des Fensters. 3. Auf die Message warten, daß das Gadget betätigt wurde. 4. Fenster schließen und Ende. Davon abgesehen, daß in Sachen Punkt 3 noch keine weiteren Informationen gegeben sind, ist in diesem Ablaufplan ein Fehler. Aufmerksame Leser (Folge I !!) werden ihn bemerkt haben. Er liegt in Punkt 1 : der User könnte so lange er wollte die linke obere Ecke des Fensters anstarren, es würde einfach kein CloseGadget entstehen. Dazu muß man nämlich bekanntermaßen in der NewWindow-Struktur das WINDOWCLOSE-Flag setzen ! Nun zu dem alles entscheidenden Punkt 3. Hier stellt Exec uns z.B. folgende Befehle zur Verfügung : MessagePtr := GetMsg(port : MsgPortPtr); Diese Funktion holt die nächste Message aus einem MessagePort, einer Art "Hafen" für ankommende Nachrichten. Sie stehen dort Schlange, bis jemand sie nimmt und zurückschickt. Da die Message-Befehle in "Include:exec/Ports.i" definiert sind (wird allerdings von "Include:intuition/ intuition.i" automatisch eingebunden), müssen es nicht unbedingt IntuiMessages sein, die empfangen werden. GetMsg liefert also keinen IntuiMessagePtr zurück ! Darauf muß geachtet werden, da sonst bei der Compilierung eine Fehlermeldung auftritt ! Der MsgPort, an den "unsere" Messages ankommen, befindet sich im Window und heißt "UserPort", weil er eben die Schnittstelle zum User, dem Anwender, ist. ReplyMsg(mess : MessagePtr); Wie oben beschrieben, sollten einmal angenommene Messages wieder zurückgeschickt werden, da sie Speicherplatz belegen. Bildlich gesprochen, gammeln sie immer noch in der Schlange herum.... Hier gilt wieder : Exec bezieht sich NICHT auf IntuiMessages ! Nun kommen wir zu einem häufigen Problem im Umgang mit Messages, das auch in unserem Programm auftritt. Wir wollen ja warten, bis der User sich bequemt, das Gadget anzuklicken. Wie realisiert man nun das ? Nun ja, eine Möglichkeit wäre REPEAT myintuimessage:=Address (GetMsg (mywindow^.UserPort)); UNTIL myintuimessage<>NIL; Dies ist eigentlich logisch und auch schön strukturiert. Auf einem PC mag dies das Maß aller Dinge sein. Nicht so auf unserem Amiga, der ja bekanntermaßen ein Multitasking-System ist, ein Computer also, auf dem viele Programme gleichzeitig laufen können. Benutzt man nun obige Routine in einem Programm, so zeigt es sich, daß parallel laufende Programme extrem langsamer werden, der Prozessor also "in die Knie geht". Dies liegt daran, das hier ständig ein Befehl abgearbeitet wird. Einfacher und wesentlich systemkonformer geht es mit der Exec-Routine MessagePtr := WaitPort (port : MsgPortPtr); Sie wartet so lange, bis eine Message im MsgPort ankommt. Bis dahin wird der Prozessor NICHT belastet ! Das ist ein enormer Vorteil. Unsere Warteroutine sieht nun also so aus : myintuimessage:=Address (WaitPort (mywindow^.UserPort)); myintuimessage:=Address (GetMsg (mywindow^.UserPort)); Nun werfen sich noch zwei Fragen auf : 1. Was sollen eigentlich das "Address" vor den Funktionsaufrufen und was sollen die Klammern drumherum ? 2. Warum folgt auf das WaitPort nochmal ein GetMsg ? Die Antworten : 1. Das "Address" ist ein sogenannter "Cast", der aus der speziellen Erwartung "MessagePtr" (was ja nicht zu IntuiMessagePtr passt !), eine allgemeine Address macht, die auch mit einem IntuiMessagePtr gleichgesetzt werden kann, ohne daß PCQ eine Fehlermeldung ausgibt. Die Klammern um den Funktionsaufruf kennzeichnen die Variable, in diesem Fall den Rückgabeparameter der Funktion, die "gecastet" werden soll. 2. WaitPort liefert NICHT den Zeiger auf die angekommene Message zurück ! Diesen Fehler darf man AUF KEINEN Fall begehen ! Deshalb : nach jedem WaitPort muß ein GetMsg kommen ! Hier möchte ich auch gleich darauf hinweisen, daß ich aus diesem Grund mein "Include:Exec/Ports.i"-File ein wenig modifiziert habe, und dort nun nur noch zu lesen ist : Procedure WaitPort (port : MessagePortPtr); Nur für diesen Kurs schreibe ich die WaitPorts in die ursprüngliche Form um und deshalb kann es sein, daß ich es einmal vergesse und in einem Beispiel auch eine WaitPort-Prozedur, statt einer Funktion aufrufe ! Dies bitte ich im voraus zu entschuldigen ! Nun aber, nach so viel grauer Theorie, zu dem Beispielprogramm : Program KursProgramm; { Listing für den AMIGAGadget/Purity - Pascalkurs , } { Erste Schritte in der Messageprogrammierung } {$I "Include:Intuition/Intuition.I" } {$I "Include:Exec/Libraries.I" } VAR myscreen : ScreenPtr; mywindow : WindowPtr; PROCEDURE CloseDisplay; VAR i : Integer; { Sollte ein Teil des Displays offen sein, dann wird er von } { dieser Routine geschlossen. } BEGIN IF mywindow<>NIL THEN CloseWindow (mywindow); IF myscreen<>NIL THEN CloseScreen (myscreen); END; PROCEDURE BreakProgram (reason : STRING); { Diese Routine schließt alles bisher geöffnete, druckt den } { Fehlergrund aus und bricht dann das Programm ab. } BEGIN CloseDisplay; WRITELN ('Program error : ',reason); Exit (42); END; PROCEDURE OpenDisplay; { Diese Routine erstellt ein Display. } CONST mynewscreen : NewScreen = (0,0,640,256,2,0,1,HIRES, CUSTOMSCREEN_f,NIL, NIL,NIL,NIL); NewWin : NewWindow = (160,50,320,128,0,1,CLOSEWINDOW_f, SMART_REFRESH+WINDOWDRAG+ ACTIVATE+SMART_REFRESH+WINDOWCLOSE,NIL,NIL, "Click to Close - Dank Messages !", NIL,NIL,0,0,0,0,CUSTOMSCREEN_f); BEGIN myscreen := OpenScreen (Adr(mynewscreen)); IF myscreen=NIL THEN BreakProgram ("Couldn't open Screen"); NewWin.Screen:=myscreen; mywindow := OpenWindow (Adr(NewWin)); IF mywindow=NIL THEN BreakProgram ("Couldn't open Window"); END; PROCEDURE WarteAufKlick; { Unsere, oben entwickelte Warte-Routine. } VAR myintuimessage : IntuiMessagePtr; BEGIN myintuimessage := Address (WaitPort (mywindow^.UserPort)); myintuimessage := Address (GetMsg (mywindow^.UserPort)); ReplyMsg (Address(myintuimessage)); { schön brav die Message zurückschicken und dann good bye ! } END; BEGIN OpenDisplay; WarteAufKlick; CloseDisplay; END. Es steht nun jedem frei, selbst mit den verschiedensten Flags zu experimentieren, bis wir im nächsten Teil tiefer in die Materie einsteigen. Weitere Informationen, Antworten auf Fragen und und und gibt es natürlich im Leserforum des Gadgets oder direkt bei mir Andreas Neumann Auf dem Ruhbühl 151 7997 Immenstaad Ansonsten bis zur nächsten Purity, wenn es wieder heißt Ewig währt am längsten oder wie programmiere ich Pascal ? © 1992 by Andreas Neumann für AmigaGadget von Nils Kassube und Purity von Steppenbrand und Diesel